namespace SharpTalk.Core.Services
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.ServiceModel;
    using System.Threading.Tasks;
    using Diagnostic;
    using Extensions;
    using Requests;
    using ServiceAgents;

    /// <summary>
    /// 
    /// </summary>
    public sealed class MessageDeliveryService : Traceable
    {
        private readonly TimeSpan _deliveryTimeOutDelay = TimeSpan.FromMilliseconds(-1); // Infinite by default
        private readonly string _endpointconf = null;

        /// <summary>
        /// Creates a new instance of the <see cref="T:SharpTalk.Core.Services.MessageDeliveryService"/> class.
        /// </summary>
        public MessageDeliveryService(string endpointConfigurationName)
        {
            if (endpointConfigurationName == null) throw new ArgumentNullException("endpointConfigurationName");
            _endpointconf = endpointConfigurationName;
        }
        /// <summary>
        /// Creates a new instance of the <see cref="T:SharpTalk.Core.Services.MessageDeliveryService"/> class.
        /// </summary>
        public MessageDeliveryService(string endpointConfigurationName, ITraceProvider traceProvider)
            :this(endpointConfigurationName)
        {
            TraceProvider = traceProvider;
        }
        /// <summary>
        /// Creates a new instance of the <see cref="T:SharpTalk.Core.Services.MessageDeliveryService"/> class.
        /// </summary>
        public MessageDeliveryService(string endpointConfigurationName, TimeSpan deliveryTimeoutDelay)
            : this(endpointConfigurationName)
        {
            _deliveryTimeOutDelay = deliveryTimeoutDelay;
        }
        /// <summary>
        /// Creates a new instance of the <see cref="T:SharpTalk.Core.Services.MessageDeliveryService"/> class.
        /// </summary>
        public MessageDeliveryService(string endpointConfigurationName, TimeSpan deliveryTimeoutDelay, ITraceProvider traceProvider)
            : this(endpointConfigurationName, deliveryTimeoutDelay)
        {
            TraceProvider = traceProvider;
        }

        public bool DeliverMessage(MessageSendRequest request, User user)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            return request.IsValid() && DeliverMessage(request, new[] { user });
        }

        public bool DeliverMessage(MessageSendRequest request, IEnumerable<User> users)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            if (!request.IsValid()) return false;

            Task.Factory.StartNew(() => DeliverMessageAsync(request, users));
            return true;
        }

        private void DeliverMessageAsync(MessageSendRequest request, IEnumerable<User> users)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            try
            {
                request.Owner = UserService.GetSystemUser().Guid;
                foreach (var user in users)
                {
                    if (string.IsNullOrEmpty(user.Uri))
                    {
                        TraceError("User {0:N} has an invalid URI. Value:\"{1}\"", user.Guid, user.Uri);
                        continue;
                    }

                    try
                    {
                        DeliverRequestTo(request, user.Uri);
                    }
                    catch (Exception exp)
                    {
                        TraceError("Delivering message to user {0:N} (URI:\"{1}\") failed. Exception: {2}. Details: {3}", user.Guid, user.Uri, exp.GetType(), exp.Message);
                    }
                }
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp, true);
            }
        }

        private void DeliverRequestTo(MessageSendRequest request, string uri)
        {
            using (var agent = new MessageDeliveryServiceAgent(_endpointconf, new EndpointAddress(string.Format("{0}/Messages",uri))))
            {
                var ar = agent.BeginDeliverMessage(request, null, null);
                ar.AsyncWaitHandle.WaitOne(_deliveryTimeOutDelay);
                ar.AsyncWaitHandle.Close();
                var response = agent.EndDeliverMessage(ar);

                if(response.HasError)
                    TraceError("Delivering message to {0} has raised an exception of type {1}. Details: {2}",uri,response.Error.Type,response.Error.Message);
            }

        }
    }
}